﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Seemplest.Core.DataAccess.Attributes;
using Seemplest.Core.DataAccess.DataRecords;
using Seemplest.Core.DataAccess.Mapping;
using Seemplest.Core.Exceptions;
using SoftwareApproach.TestingExtensions;

namespace Seemplest.MsSql.UnitTests.DataAccess
{
    [TestClass]
    public class RecordMetadataManagerTest
    {
        [TestInitialize]
        public void Initialize()
        {
            RecordMetadataManager.Reset();
        }

        [TestMethod]
        public void SimplePocoMetadataWorks()
        {
            // --- Act
            var meta = RecordMetadataManager.GetMetadata(typeof (SimplePoco1));

            // --- Assert
            meta.ShouldNotBeNull();
            meta.IsSimplePoco.ShouldBeTrue();
            meta.IsImmutable.ShouldBeFalse();
            meta.SchemaName.ShouldBeNull();
            meta.TableName.ShouldBeNull();
            meta.DataColumns.ShouldHaveCountOf(3);
            
            var col = meta["IntProp"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("IntProp");
            col.ColumnName.ShouldEqual("IntProp");
            col.ClrType.ShouldEqual(typeof(int));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();

            col = meta["StringProp"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("StringProp");
            col.ColumnName.ShouldEqual("StringProp");
            col.ClrType.ShouldEqual(typeof(string));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();

            col = meta["NewProp"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("RenamedProp");
            col.ColumnName.ShouldEqual("NewProp");
            col.ClrType.ShouldEqual(typeof(int));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();
        }

        [TestMethod]
        public void SimplePocoWithTableNameWorks()
        {
            // --- Act
            var meta = RecordMetadataManager.GetMetadata(typeof(SimplePoco2));

            // --- Assert
            meta.ShouldNotBeNull();
            meta.TableName.ShouldEqual("Poco2");
            meta.SchemaName.ShouldBeNull();
        }

        [TestMethod]
        public void SimplePocoWithSchemaNameWorks()
        {
            // --- Act
            var meta = RecordMetadataManager.GetMetadata(typeof(SimplePoco3));

            // --- Assert
            meta.ShouldNotBeNull();
            meta.TableName.ShouldEqual("Poco3");
            meta.SchemaName.ShouldEqual("Poco");
        }

        [TestMethod]
        public void SimplePocoMetadataWorksWithGenericMethod()
        {
            // --- Act
            var meta = RecordMetadataManager.GetMetadata<SimplePoco1>();

            // --- Assert
            meta.ShouldNotBeNull();
            meta.IsSimplePoco.ShouldBeTrue();
            meta.IsImmutable.ShouldBeFalse();
            meta.SchemaName.ShouldBeNull();
            meta.TableName.ShouldBeNull();
            meta.DataColumns.ShouldHaveCountOf(3);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void GetMetadataFailsWithNull()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(null);
        }

        [TestMethod]
        [ExpectedException(typeof(MissingTableNameException))]
        public void RecordFailsWithNoTableName()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(typeof (Record1));
        }

        [TestMethod]
        public void DataRecordWorks()
        {
            // --- Act
            // --- Must work for the second request as well
            RecordMetadataManager.GetMetadata(typeof(Record2));
            var meta = RecordMetadataManager.GetMetadata(typeof(Record2));

            // --- Assert
            meta.ShouldNotBeNull();
            meta.SchemaName.ShouldEqual("MySchema");
            meta.TableName.ShouldEqual("Record2");
            meta.IsImmutable.ShouldBeFalse();
            meta.IsSimplePoco.ShouldBeFalse();
            meta.DataColumns.ShouldHaveCountOf(5);
            meta.PrimaryKeyColumns.ShouldHaveCountOf(1);

            var col = meta["Id"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("Id");
            col.ColumnName.ShouldEqual("Id");
            col.ClrType.ShouldEqual(typeof(int));
            col.IsAutoGenerated.ShouldBeTrue();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldEqual(0);

            col = meta["DisplayName"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("Name");
            col.ColumnName.ShouldEqual("DisplayName");
            col.ClrType.ShouldEqual(typeof(string));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();

            col = meta["Description"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("Description");
            col.ColumnName.ShouldEqual("Description");
            col.ClrType.ShouldEqual(typeof(string));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();

            col = meta["RowVersion"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("RowVersion");
            col.ColumnName.ShouldEqual("RowVersion");
            col.ClrType.ShouldEqual(typeof(byte[]));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeFalse();
            col.IsVersionColumn.ShouldBeTrue();
            col.PkOrder.ShouldBeNull();

            col = meta["Counter"];
            col.ShouldNotBeNull();
            col.Name.ShouldEqual("Counter");
            col.ColumnName.ShouldEqual("Counter");
            col.ClrType.ShouldEqual(typeof(decimal));
            col.IsAutoGenerated.ShouldBeFalse();
            col.IsCalculated.ShouldBeTrue();
            col.IsVersionColumn.ShouldBeFalse();
            col.PkOrder.ShouldBeNull();
        }

        [TestMethod]
        public void DataRecordDescriptorIndexerWorks()
        {
            // --- Act
            var meta = RecordMetadataManager.GetMetadata(typeof(Record2));

            // --- Assert
            meta.ShouldNotBeNull();
            meta.ContainsColumn("Id").ShouldBeTrue();
            meta.ContainsColumn("DisplayName").ShouldBeTrue();
            meta.ContainsColumn("Description").ShouldBeTrue();
            meta.ContainsColumn("RowVersion").ShouldBeTrue();
            meta.ContainsColumn("Counter").ShouldBeTrue();

            meta.ContainsColumn("Guid").ShouldBeFalse();
            meta.ContainsColumn("Ignored").ShouldBeFalse();

            meta.DataColumns.ShouldContain(meta[0]);
            meta.DataColumns.ShouldContain(meta[1]);
            meta.DataColumns.ShouldContain(meta[2]);
            meta.DataColumns.ShouldContain(meta[3]);
            meta.DataColumns.ShouldContain(meta[4]);
        }

        [TestMethod]
        [ExpectedException(typeof(NullReferenceException))]
        public void SourceConverterWithNullSourceTypeFails()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(typeof(Record3));
        }

        [TestMethod]
        [ExpectedException(typeof(NullReferenceException))]
        public void SourceConverterWithNullConverterTypeFails()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(typeof(Record4));
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidCastException))]
        public void SourceConverterWithInvalidConverterTypeFails1()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(typeof(Record5));
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidCastException))]
        public void SourceConverterWithInvalidConverterTypeFails2()
        {
            // --- Act
            RecordMetadataManager.GetMetadata(typeof(Record6));
        }

        internal class SimplePoco1
        {
            public int IntProp { get; set; }
            public string StringProp { get; set; }
            // ReSharper disable UnusedMember.Local
            private Guid PrivProp { get; set; }
            // ReSharper restore UnusedMember.Local
            // ReSharper disable UnusedAutoPropertyAccessor.Local
            public decimal DecProp { get; private set; }
            // ReSharper restore UnusedAutoPropertyAccessor.Local
            [ColumnName("NewProp")]
            public int RenamedProp { get; set; }
        }

        [TableName("Poco2")]
        internal class SimplePoco2
        {
            public int Prop1 { get; set; }
            public int Prop2 { get; set; }
        }

        [TableName("Poco3")]
        [SchemaName("Poco")]
        internal class SimplePoco3
        {
            public int Prop1 { get; set; }
            public int Prop2 { get; set; }
        }

        internal class Record1 : DataRecord<Record1>
        {
        }

        [SchemaName("MySchema")]
        [TableName("Record2")]
        internal class Record2 : DataRecord<Record2>
        {
            [AutoGenerated]
            [PrimaryKey]
            public int Id { get; set; }

            [ColumnName("DisplayName")]
            public string Name { get; set; }

            public string Description { get; set; }

            [VersionColumn]
            public byte[] RowVersion { get; set; }

            [Calculated]
            public decimal Counter { get; set; }

            // ReSharper disable UnusedAutoPropertyAccessor.Local
            public Guid Guid { get; private set; }
            // ReSharper restore UnusedAutoPropertyAccessor.Local

            [IgnoreProperty]
            public bool Ignored { get; set; }
        }

        [TableName("Record3")]
        internal class Record3 : DataRecord<Record3>
        {
            public int Id { get; set; }

            [SourceConverter(null, typeof(SampleSourceConverter))]
            public string Name { get; set; }
        }

        [TableName("Record4")]
        internal class Record4 : DataRecord<Record4>
        {
            public int Id { get; set; }

            [SourceConverter(typeof(string), null)]
            public string Name { get; set; }
        }

        [TableName("Record5")]
        internal class Record5 : DataRecord<Record5>
        {
            public int Id { get; set; }

            [SourceConverter(typeof(string), typeof(string))]
            public string Name { get; set; }
        }

        [TableName("Record6")]
        internal class Record6 : DataRecord<Record6>
        {
            public int Id { get; set; }

            [SourceConverter(typeof(string), typeof(SampleSourceConverter2))]
            public string Name { get; set; }
        }

        class SampleSourceConverter : DualConverterBase<string, string>
        {
            public override string ConvertFromDataType(string dataType)
            {
                return dataType;
            }

            public override string ConvertToDataType(string clrObject)
            {
                return clrObject;
            }
        }

        class SampleSourceConverter2 : DualConverterBase<int, int>
        {
            public override int ConvertFromDataType(int dataType)
            {
                return dataType;
            }

            public override int ConvertToDataType(int clrObject)
            {
                return clrObject;
            }
        }
    }
}
